﻿// Inner Fire 游戏引擎库
// Armature - 骨骼
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层(4)
// 用于提取模型中的骨骼数据。方便动画和模型的统一管理
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-03-13

#pragma once

#include <DirectXMath.h>
#include <EASTL/unordered_map.h>
#include <EASTL/vector.h>
#include <assimp/scene.h> // 包含Assimp的场景结构体
#include <dx/mesh/skin_animator.h>
#include <game/animation/animation_clip.h>
#include <game/animation/armature_node.h>
#include <game/animation/avatar.h>
#include <kfile.h>
#include <memory>
#include <ref_info.h>
#include <static_string.h>
#include <cached_file.h>

namespace ifire::game {
class Avatar;
class HumanoidFrame;
class BinaryModel;

struct BoneInfo {
  // 此骨骼的ID
  int id;
  // 此骨骼的名称
  ikit::sstr name;
  /**
   * 将顶点从网格空间转换到绑定姿势下骨骼空间的矩阵。
   *
   * 该矩阵描述了在骨架绑定时，网格在该骨骼局部空间中的位置。
   * 因此，它可以用于直接确定目标顶点的位置，
   * 前提是已知骨骼在动画时的世界空间变换，
   * 以及顶点在网格空间中的位置。
   *
   * 它有时被称为逆绑定矩阵（inverse-bind matrix），
   * 或逆绑定姿势矩阵（inverse bind pose matrix）。
   */
  IMat4 offset_mat;

  BoneInfo(int bone_id, ikit::sstr bone_name, IMat4 offset_matrix)
      : id(bone_id), name(bone_name), offset_mat(offset_matrix) {}
};

class Armature {
public:
  // 通过Assimp的载入进行初始化
  Armature(const aiScene* scene, bool is_simplify_node, bool convert_units_);
  // 通过一个CachedFile进行初始化
  Armature(ikit::CachedFile& f);

  Armature(const Armature& lhs) = delete;
  Armature& operator=(const Armature& lhs) = delete;
  ~Armature() = default;

  // 设置友元
  friend class HumanoidFrame;
  friend class BinaryModel;

  // 获取当前Bone的ID。
  int GetBoneId(ikit::sstr name) const;
  // 获取一个骨骼
  BoneInfo& GetBone(int bone_id);
  // 创建一个Bone。接受逆绑定矩阵做为参数
  BoneInfo& AddBone(ikit::sstr name, IMat4 offset_matrix);
  // 打印骨骼的逆绑定矩阵
  void PrintBoneData(const char* file_name);

  // 将每个Node和其上的骨骼进行连接
  void AttachBonesToNodes();
  // 对一个节点进行骨骼的绑定
  void AttachBone(ArmatureNode* node);

  // 将一个变换矩阵加上骨骼的Offset，设置为最终的矩阵
  void GetFinal(const MatrixList& transforms, eastl::vector<IMat4>& finals);

  // 打印节点信息
  void PrintNode(bool with_bone = true) const;
  void PrintNodeHierarchy(
      const ArmatureNode* node, bool with_bone = true) const;
  // 设置RootMotion的Node
  void SetRootMotionNode(ikit::sstr name);
  // 通过Avatar来配置项目的ID
  void SetByAvatar(Avatar* avatar);

  // 将项目保存到CachedFile中
  void Save(ikit::CachedFile& f);

  //
  // 属性访问
  //
  size_t GetBoneCount() const { return bones_.size(); }
  const Avatar* GetAvatar() const;
  const ArmatureNode* Root() const { return node_->Root(); }
  const ArmatureTree* GetNodeTree() const { return node_.get(); }

private:
  // 打印骨骼的逆绑定矩阵（用于Hierarchy递归）
  void PrintBoneHierarchy(
      FXMMATRIX parent_mat, ikit::file_printer& desc, const ArmatureNode* node);

private:
  // 全局的逆变换矩阵（基本上用不到）
  IMat4 global_inverse_transform_;

  // BONE的印射。Key为static_string的uid，Value为骨骼的ID
  eastl::unordered_map<size_t, int> bone_map_;
  // 骨骼列表
  eastl::vector<BoneInfo> bones_;
  // 根节点
  std::unique_ptr<ArmatureTree> node_;
  // 是否简化骨骼（把$AssimpFbx$这类骨骼都去掉）
  bool is_simplify_node_;
  // 此骨骼的通用骨架
  Avatar* avatar_;
};

using ArmaturePtr = std::unique_ptr<Armature>;

} // namespace ifire::game